package cc.rengu.igas.channel.upqc.core.packswitch;

import cc.rengu.igas.channel.upqc.common.constant.UpqcConfigConstant;
import cc.rengu.igas.channel.upqc.common.constant.UpqcParamConstant;
import cc.rengu.igas.channel.upqc.common.constant.UpqcTreeNodeConstant;
import cc.rengu.igas.channel.upqc.common.enums.RespCodeEnum;
import cc.rengu.igas.channel.upqc.common.util.UpqcSecurityUtil;
import cc.rengu.igas.channel.upqc.facade.base.BaseRequest;
import cc.rengu.jradp.packetswitch.IncomingPackSwitchImpl;
import cc.rengu.jradp.packetswitch.OutgoingPackSwitchImpl;
import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.constant.TreeNodeConstant;
import cc.rengu.oltp.service.common.dao.BizMsgInfoMapper;
import cc.rengu.oltp.service.common.dao.CertInfoMapper;
import cc.rengu.oltp.service.common.dao.SecPlanInfoMapper;
import cc.rengu.oltp.service.common.dao.impl.BizMsgInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.CertInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SecPlanInfoMapperImpl;
import cc.rengu.oltp.service.common.entity.*;
import cc.rengu.oltp.service.common.enums.ChannelEnum;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.realize.HsmService;
import cc.rengu.oltp.service.realize.SysParamService;
import cc.rengu.oltp.service.realize.impl.HsmServiceImpl;
import cc.rengu.oltp.service.realize.impl.SysParamServiceImpl;
import cc.rengu.oltp.utility.util.*;
import cc.rengu.utility.cache.UnifiedCache;
import cc.rengu.utility.log.RgLog;
import cc.rengu.utility.log.RgLogger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.*;

/**
 * 银联二维码通讯报文解包前后、组前包后实现类
 */
public class UpqcClientPackSwitch implements IncomingPackSwitchImpl, OutgoingPackSwitchImpl {

    @Override
    public byte[] beforeInComPackSwitch(byte[] bytes) throws Exception {
        RgLogger rglog = RgLog.getLogger(this.getClass().getName());
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String recvData = new String(bytes);
        /*增加银联返回报文打印*/
        rglog.debug("银联返回报文:<{}>", recvData);
        /*获取业务报文登记标志*/
        String msgRegisterFlag = xmlTreeUtil.getXmlTreeStringValue(UpqcTreeNodeConstant.MSG_REG_FLAG);
        if (AppParamConstant.YES.equals(msgRegisterFlag)) {
            /*更新业务报文信息*/
            updateBizMsgInfo(rglog, recvData);
        }
        String publicKey = null;
        if (!recvData.isEmpty()) {
            /* 获取敏感信息加密证书，去银联时安全计划存储在内部树中 */
            String secPlanInfoListString = xmlTreeUtil.getXmlTreeStringValue(UpqcTreeNodeConstant.SEC_PLAN_INFO_LIST);
            List<SecPlanInfo> secPlanInfoList = JSONArray.parseArray(secPlanInfoListString, SecPlanInfo.class);
            Optional<SecPlanInfo> checkSignSecPlanOpt = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(UpqcParamConstant.VERIFY_CERT_TYPE)).findFirst();
            if (checkSignSecPlanOpt.isPresent()) {
                CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
                CertInfo certInfo = certInfoMapper.selectCertInfoByPrimaryKey(checkSignSecPlanOpt.get().getInstId() + checkSignSecPlanOpt.get().getSecPlanId() + checkSignSecPlanOpt.get().getSecPlanType());
                if (certInfo == null) {
                    rglog.error("根据索引<{}>获取验签公钥失败!", checkSignSecPlanOpt.get().getInstId() + checkSignSecPlanOpt.get().getSecPlanId() + checkSignSecPlanOpt.get().getSecPlanType());
                    xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respCode, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                    xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respDesc, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                    return new byte[0];
                }
                if ("0".equals(certInfo.getCertSaveType())) {
                    publicKey = certInfo.getCertValue();
                } else if ("1".equals(certInfo.getCertSaveType())) {
                    Map<String, String> signDataMap = UpqcSecurityUtil.convertResultString2Map(recvData);
                    String signature = signDataMap.get(UpqcTreeNodeConstant.signature);
                    if (null == signature || signature.isEmpty()) {
                        /* 签名方法或者签名域为空，直接返回失败 */
                        rglog.error("签名验证失败!");
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respCode, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respDesc, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                        return new byte[0];
                    }
                    String stringData = covertMap2String(signDataMap);
                    String algorithmType = "SM3";//加密机默认国密摘要
                    rglog.debug("使用加密机加密,算法类型:<{}>,", certInfo.getAlgorithmType());
                    if (certInfo.getAlgorithmType() != null) {
                        if (certInfo.getAlgorithmType().contains("SHA256")) {
                            algorithmType = "SHA256";
                        } else if (certInfo.getAlgorithmType().contains("RSA")) {
                            algorithmType = "SHA1";
                        }
                    }
                    HsmService hsmService = new HsmServiceImpl();
                    if (!hsmService.hsmVerifySignString(null, certInfo.getCertId(), stringData, algorithmType, signature)) {
                        rglog.error("签名验证失败!");
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respCode, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respDesc, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                        return new byte[0];
                    }
                } else if ("2".equals(certInfo.getCertSaveType())) {
                    /*优先从系统缓存读取*/
                    publicKey = (String) UnifiedCache.get(UpqcParamConstant.CERT_KEY_CACHE, certInfo.getCertIndex());
                    if (StringUtil.isEmptyOrNull(publicKey)) {
                        /*从本地证书文件获取*/
                        RSAPublicKey rsaPublicKey = RSAUtil.getPublicKeyFromCertFile(certInfo.getCertSavePath());
                        if (rsaPublicKey != null) {
                            if (UnifiedCache.set(UpqcParamConstant.CERT_KEY_CACHE, certInfo.getCertIndex(),
                                    Base64.encodeBase64String(rsaPublicKey.getEncoded())) != 0) {
                                rglog.error("设置证书缓存索引号{}缓存失败", certInfo.getCertIndex());
                            }
                            publicKey = Base64.encodeBase64String(rsaPublicKey.getEncoded());
                        } else {
                            rglog.error("通过文件路径获取证书失败<{}>", certInfo.getCertSavePath());
                            return new byte[0];
                        }
                    }
                } else {
                    return bytes;
                }
                /* 验证签名 */
                if (UpqcSecurityUtil.verifySign(recvData, publicKey)) {
                    rglog.info("签名验证成功!");
                    return bytes;
                } else {
                    rglog.error("签名验证失败!");
                    xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respCode, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                    xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respDesc, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                    return new byte[0];
                }
            } else {
                rglog.error("获取验签公钥失败，验签失败!");
                xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respCode, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.respDesc, RespCodeEnum.VERIFY_SIGN_ERROR.getRespCode());
                return new byte[0];
            }
        }
        return bytes;
    }

    @Override
    public int afterInComPackSwitch() throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        if (xmlTreeUtil.isExist(TreeNodeConstant.ORDER_AMT)) {
            xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.ORDER_AMT, AmountUtil.changeF2Y(xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.ORDER_AMT)));
        }
        return 0;
    }

    @Override
    public int beforeOutgoPackSwitch() throws Exception {
        RgLogger rglog = RgLog.getLogger(this.getClass().getName());
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String instId = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.INST_ID);
        if (instId == null) {
            instId = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.HEADER_INST_ID);
        }
        if (xmlTreeUtil.isExist(TreeNodeConstant.ORDER_AMT)) {
            xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.ORDER_AMT, AmountUtil.changeY2F(xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.ORDER_AMT)));
        }
        if (xmlTreeUtil.isExist(UpqcTreeNodeConstant.origTxnAmt)) {
            xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.origTxnAmt, AmountUtil.changeY2F(xmlTreeUtil.getXmlTreeStringValue(UpqcTreeNodeConstant.origTxnAmt)));
        }
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParam = null;
        /* 加载公共信息 */
        sysParam = sysParamService.getSysParamInfo(instId, UpqcConfigConstant.UPQC_CONF_NODE, UpqcConfigConstant.UPQC_VERSION);
        if (sysParam != null) {
            xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.version, sysParam.getParamValue());
        }
        sysParam = sysParamService.getSysParamInfo(instId, UpqcConfigConstant.UPQC_CONF_NODE, UpqcConfigConstant.UPQC_SIGN_TYPE);
        if (sysParam != null) {
            xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.signType, sysParam.getParamValue());
        }
        sysParam = sysParamService.getSysParamInfo(instId, UpqcConfigConstant.UPQC_CONF_NODE, UpqcConfigConstant.UPQC_BACK_URL);
        if (sysParam != null) {
            xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.backUrl, sysParam.getParamValue());
        }
        /* 获取通道配置信息 */
        DstChannelInfo dstChannelInfo = (DstChannelInfo) xmlTreeUtil.getXmlTreeObjectValue(UpqcTreeNodeConstant.DST_CHANNEL_INFO, DstChannelInfo.class);
        if (null == dstChannelInfo) {
            rglog.error("获取通道<{}>配置信息失败!", UpqcParamConstant.TUNL_ID_UPQC);
            throw new BizException(RespCodeEnum.CHANNEL_CONFIG_NOT_EXIST.getRespCode(), RespCodeEnum.CHANNEL_CONFIG_NOT_EXIST.getRespDesc());
        }
        /* 根据通道安全计划获取安全计划 */
        SecPlanInfoMapper secPlanInfoMapper = new SecPlanInfoMapperImpl();
        List<SecPlanInfo> secPlanInfoList = secPlanInfoMapper.selectSecPlanInfoBySecPlanId(instId, dstChannelInfo.getSecPlanId());
        if (null != secPlanInfoList && !secPlanInfoList.isEmpty()) {
            xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.SEC_PLAN_INFO_LIST, JSON.toJSONString(secPlanInfoList));
            /* 加签证书 */
            Optional<SecPlanInfo> secPlanInfoOpt = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(UpqcParamConstant.SIGN_CERT_TYPE)).findFirst();
            if (secPlanInfoOpt.isPresent()) {
                CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
                CertInfo certInfo = certInfoMapper.selectCertInfoByPrimaryKey(secPlanInfoOpt.get().getInstId() + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType());
                if (null != certInfo) {
                    rglog.debug("certInfo = <{}>", JSONObject.toJSONString(certInfo));
                    if ("2".equals(certInfo.getCertSaveType())) {
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.certId, UpqcSecurityUtil.getCertIdFromCertFile(certInfo.getCertSavePath(), certInfo.getCertPassword()));
                    } else {
                        xmlTreeUtil.setXmlTreeStringValue(UpqcTreeNodeConstant.certId, certInfo.getCertId());
                    }
                }
            }
        }
        xmlTreeUtil.setXmlTreeStringValue("_MESSAGEHEAD/http/Content-Type", "application/x-www-form-urlencoded");
        return 0;
    }

    @Override
    public byte[] afterOutgoPackSwitch(byte[] bytes) throws Exception {
        RgLogger rglog = RgLog.getLogger(this.getClass().getName());
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        /*获取业务报文登记标志*/
        String msgRegisterFlag = xmlTreeUtil.getXmlTreeStringValue(UpqcTreeNodeConstant.MSG_REG_FLAG);
        String content = new String(bytes);
        rglog.debug("未签名报文：{}", content);
        if (!content.isEmpty()) {
            /* 获取敏感信息加密证书 */
            String secPlanInfoListString = xmlTreeUtil.getXmlTreeStringValue(UpqcTreeNodeConstant.SEC_PLAN_INFO_LIST);
            List<SecPlanInfo> secPlanInfoList = JSONArray.parseArray(secPlanInfoListString, SecPlanInfo.class);
            Optional<SecPlanInfo> secPlanInfoOpt = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(UpqcParamConstant.SIGN_CERT_TYPE)).findFirst();
            if (secPlanInfoOpt.isPresent()) {
                CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
                CertInfo certInfo = certInfoMapper.selectCertInfoByPrimaryKey(secPlanInfoOpt.get().getInstId() + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType());
                /* 加签 */
                String signData = null;
                if ("0".equals(certInfo.getCertSaveType())) {
                    signData = UpqcSecurityUtil.sign(content, certInfo.getCertValue());
                } else if ("1".equals(certInfo.getCertSaveType())) {
                    String algorithmType = "SM3";//加密机默认国密摘要
                    rglog.debug("使用加密机加密,算法类型:<{}>,", certInfo.getAlgorithmType());
                    if (certInfo.getAlgorithmType() != null) {
                        if (certInfo.getAlgorithmType().contains("SHA256")) {
                            algorithmType = "SHA256";
                        } else if (certInfo.getAlgorithmType().contains("RSA")) {
                            algorithmType = "SHA1";
                        }
                    }
                    Map<String, String> signDataMap = UpqcSecurityUtil.convertResultString2Map(content);
                    String stringData = covertMap2String(signDataMap);
                    HsmService hsmService = new HsmServiceImpl();
                    String sign = hsmService.hsmGenerateSignString(null, certInfo.getCertId(), stringData, algorithmType);
                    signDataMap.put(UpqcTreeNodeConstant.signature, sign);
                    signData = getRequestParamString(signDataMap, UpqcParamConstant.UTF_8_ENCODING);

                } else if ("2".equals(certInfo.getCertSaveType())) {
                    /*优先从系统缓存读取*/
                    RSAPrivateKey privateKey = null;
                    String priKeyStr = (String) UnifiedCache.get(UpqcParamConstant.CERT_KEY_CACHE, certInfo.getCertIndex());
                    if (priKeyStr != null) {
                        privateKey = RSAUtil.getPrivateKeyFromBase64String(priKeyStr);
                    } else {
                        /*从本地证书文件获取*/
                        List<String> aliasList = RSAUtil.getCertAliasFromPfxCertFile(certInfo.getCertSavePath(), certInfo.getCertPassword());
                        privateKey = RSAUtil.getPrivateKeyFromCertFile(certInfo.getCertSavePath(), aliasList.get(0), certInfo.getCertPassword());
                        if (privateKey != null) {
                            if (UnifiedCache.set(UpqcParamConstant.CERT_KEY_CACHE, certInfo.getCertIndex(), Base64.encodeBase64String(privateKey.getEncoded())) != 0) {
                                rglog.error("设置证书缓存索引号{}缓存失败", certInfo.getCertIndex());
                            }
                        }
                    }
                    signData = UpqcSecurityUtil.sign(content, privateKey);
                } else {
                    rglog.error("暂时不支持证书存储方式<{}>", certInfo.getCertSaveType());
                    throw new BizException(RespCodeEnum.CERT_SAVE_TYPE_ERROR.getRespCode(), RespCodeEnum.CERT_SAVE_TYPE_ERROR.getRespDesc());
                }
                /*登记业务报文表*/
                if (AppParamConstant.YES.equals(msgRegisterFlag)) {
                    registerBizMsgInfo(rglog, signData);
                }
                return signData.getBytes();
            }
        }
        return bytes;
    }

    private void registerBizMsgInfo(RgLogger rglog, String bizMsg) {
        try {
            XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
            if (bizMsg.length() > 8000) {
                rglog.error("存储转发报文超过8K，暂不登记到存储转发表:<{}>", bizMsg);
                return;
            }
            BizMsgInfo bizMsgInfo = new BizMsgInfo();
            BizMsgInfoMapper bizMsgInfoMapper = new BizMsgInfoMapperImpl();
            BaseRequest baseRequest = new BaseRequest();
            ConvertUtil.convertOutput(baseRequest);
            bizMsgInfo.setInstId(baseRequest.getHeader().getInstId());
            bizMsgInfo.setChannelId(ChannelEnum.UPQC.getChannelType());
            bizMsgInfo.setTransSeqNum(baseRequest.getHeader().getTraceNo());
            bizMsgInfo.setTxnDate(baseRequest.getHeader().getTransDate());
            bizMsgInfo.setTxnTime(baseRequest.getHeader().getTransTime());
            bizMsgInfo.setDstTxnNum(baseRequest.getHeader().getTxnNum());
            bizMsgInfo.setCreateTime(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            if (bizMsg.length() < 4000) {
                bizMsgInfo.setBizReqMsg(bizMsg);
            } else {
                bizMsgInfo.setBizReqMsg(bizMsg.substring(0, 4000));
                bizMsgInfo.setBizReqExpandMsg(bizMsg.substring(4000));
            }
            int iReturnCode = bizMsgInfoMapper.insertBizMsgInfo(bizMsgInfo);
            if (0 != iReturnCode) {
                return;
            }
            xmlTreeUtil.setXmlTreeObjectValue(UpqcTreeNodeConstant.BIZ_MSG_INFO, bizMsgInfo);
        } catch (Exception e) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            e.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
            rglog.error("登记业务报文信息表异常:<{}>", byteArrayOutputStream.toString());
        }
    }

    private void updateBizMsgInfo(RgLogger rglog, String bizRspMsg) {
        try {
            XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
            if (bizRspMsg.length() > 8000) {
                rglog.error("存储转发报文超过8K，暂不登记到存储转发表:<{}>", bizRspMsg);
                return;
            }
            if (!xmlTreeUtil.isExist(UpqcTreeNodeConstant.BIZ_MSG_INFO)) {
                rglog.error("获取请求业务报文节点失败，不更新");
                return;
            }
            BizMsgInfo bizMsgInfo = (BizMsgInfo) xmlTreeUtil.getXmlTreeObjectValue(UpqcTreeNodeConstant.BIZ_MSG_INFO, BizMsgInfo.class);
            BizMsgInfoMapper bizMsgInfoMapper = new BizMsgInfoMapperImpl();
            bizMsgInfo.setUpdateTime(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            if (bizRspMsg.length() < 4000) {
                bizMsgInfo.setBizRspMsg(bizRspMsg);
            } else {
                bizMsgInfo.setBizRspMsg(bizRspMsg.substring(0, 4000));
                bizMsgInfo.setBizRspExpandMsg(bizRspMsg.substring(4000));
            }
            int iReturnCode = bizMsgInfoMapper.updateBizMsgInfoByTransSeqNum(bizMsgInfo);
            if (0 != iReturnCode) {
                return;
            }
        } catch (Exception e) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            e.printStackTrace(new PrintWriter(byteArrayOutputStream, true));
            rglog.error("登记业务报文信息表异常:<{}>", byteArrayOutputStream.toString());
        }
    }

    private String covertMap2String(Map<String, String> data) {
        TreeMap<String, String> tree = new TreeMap<String, String>();
        Iterator<Map.Entry<String, String>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            if (UpqcTreeNodeConstant.signature.equals(en.getKey().trim())) {
                continue;
            }
            tree.put(en.getKey(), en.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            sf.append(en.getKey() + UpqcParamConstant.EQUAL + en.getValue() + UpqcParamConstant.AMPERSAND);
        }
        return sf.substring(0, sf.length() - 1);
    }

    /**
     * 将Map存储的对象，转换为key=value&key=value的字符,value值做urlEncode
     *
     * @param requestParam
     * @param coder
     * @return
     */
    private String getRequestParamString(Map<String, String> requestParam, String coder) {
        RgLogger rglog = RgLog.getLogger(this.getClass().getName());
        if (null == coder || "".equals(coder)) {
            coder = "UTF-8";
        }
        StringBuffer sf = new StringBuffer("");
        String reqstr = "";
        if (null != requestParam && 0 != requestParam.size()) {
            for (Map.Entry<String, String> en : requestParam.entrySet()) {
                try {
                    sf.append(en.getKey()
                            + "="
                            + (null == en.getValue() || "".equals(en.getValue()) ? "" : URLEncoder
                            .encode(en.getValue(), coder)) + "&");
                } catch (UnsupportedEncodingException e) {
                    rglog.error("异常信息<{}>", e.getMessage());
                    return "";
                }
            }
            reqstr = sf.substring(0, sf.length() - 1);
        }
        return reqstr;
    }
}
